跳到主要内容
Cowers://
全部文章
推理范式

ReAct 范式

ReAct = Reasoning + Acting,出自 2022 年 10 月的论文《ReAct: Synergizing Reasoning and Acting in Language Models》(Shuny…

ReAct 范式:让模型边想边做,以及它没那么神的地方

阅读提示 本笔记面向已经理解 LLM、Prompt 和工具调用基本概念、正在学 Agent 的读者。主线是:ReAct 想解决什么 → 循环怎么转 → 怎么落地写对 → 它真实的效果边界 → 后人如何改进它 → 它和今天的 Function Calling 是什么关系

前置概念见 Agent概念;框架层的工程分层见 Agent架构设计-lang系框架

⚠️ = 常见陷阱 🆚 = 对比辨析 💡 = 机制或选择建议

目录


核心概念 ReAct = Reasoning + Acting,出自 2022 年 10 月的论文《ReAct: Synergizing Reasoning and Acting in Language Models》(Shunyu Yao 等,普林斯顿大学 + Google Research,ICLR 2023)。

它的核心主张只有一句:让语言模型交替生成「推理轨迹」和「行动指令」,两者互相喂养——推理为行动定方向,行动的真实结果为推理提供纠偏依据。

论文标题里的关键词是 Synergizing(协同),不是"加上"。价值不在于"既能想又能做",而在于两者交错时产生的互补。

先做个名字消歧 前端的 React.js 是 Facebook 的 Jordan Walke 在 2013 年 5 月开源的 UI 框架,解决的是 DOM 操作和组件化问题,与本文毫无关系,只是拼写撞车。本笔记全篇讲的是 Agent 范式 ReAct。

一、ReAct 到底解决了什么问题?

在 ReAct 之前,让 LLM 完成复杂任务有两条互相独立的路线,各自都是瘸腿的。

只推理(CoT):给模型思维链示例,让它一步步推导。逻辑链条清晰,但整个过程封闭在模型参数内部,所有事实都来自训练记忆。问它今天的股价,它只能编。

只行动(Act):让模型直接输出工具调用,拿真实数据。事实可靠了,但中间没有推理痕迹,模型不知道自己为什么调这个工具,搜不到结果时也不会换个思路重试。

ReAct 的洞察是:人做事本来就是边想边干的——想一下、试一下、看结果、再想下一步。

react-paradigm-three-routes

上图从上到下读:前两行各自缺一半,第三行把两者交错起来,并用虚线标出「观察结果回流到下一轮思考」这条纠错通路。图中最值得记住的是底部两个箭头方向Thought → Action 让行动有目的,Observation → Thought 让思考有依据。少了任何一个方向,就退化成前两行之一。

术语澄清:CoT 的两种形态 CoT = Chain-of-Thought(思维链)。原始论文(Wei et al., 2022)用的是 few-shot 方式,即在 Prompt 里给出几个带推理过程的范例。

而广为流传的那句 Let's think step by step,出自另一篇论文 Zero-shot CoT(Kojima et al., 2022)——不给范例,只用一句咒语触发推理。两者常被混为一谈,面试时容易被追问。

二、Thought-Action-Observation 循环怎么转?

整个范式就是一个循环,直到模型决定结束:

Thought:      我现在知道什么?下一步该做什么?为什么?
Action:       调用哪个工具,参数是什么
Observation:  工具返回的真实结果(不是模型生成的)
→ 回到 Thought

举个例子,问:「除了苹果,还有哪家公司市值超过 2 万亿美元?」

Thought 1: 用户想知道除苹果外市值超 2 万亿的公司。我需要先查当前市值排名。
Action 1:  search[市值超过 2 万亿美元的公司]
Observation 1: 微软、英伟达、Alphabet 等公司市值均超过 2 万亿……
Thought 2: 摘要提到了三家,但没给具体数字,我需要验证其中一家的准确市值。
Action 2:  search[微软 最新市值]
Observation 2: 微软市值约 3.1 万亿美元。
Thought 3: 已验证微软符合条件,信息足够回答了。
Action 3:  finish[除苹果外,微软、英伟达等公司市值也超过 2 万亿美元……]

⚠️ finish 是一个 Action,不是独立字段 错误写法: 把最终答案写成与 Action: 平级的新标签,例如 Finish: 最终答案

实际结果: 解析器需要同时兼容两种输出格式;模型在 few-shot 里看到两套格式,容易在中途就直接吐出 Finish: 而跳过必要的工具调用。

原因: 在论文的 Wikipedia 问答环境里,finish[answer]search[entity]lookup[string]同一类东西——都是动作,只不过 finish 这个动作的语义是"终止并提交答案"。它不是一个新的输出通道。(注意 finish 本身也是该环境的设定,不是 ReAct 的通用要求,见下一节。)

正确做法: 统一只解析 Action: 一行,再判断动作名是否为 finish

name, arg = parse_action(text)      # 只有一种格式需要解析
if name == "finish":
    return arg                       # 终止条件收敛在一处
observation = TOOLS[name](arg)

Thought 不必每一步都有 论文在知识问答类任务(HotpotQA、FEVER)中让 thought 和 action 严格交替;但在决策类任务(ALFWorld、WebShop)中,thought 是稀疏的——只在关键决策点插入,中间连续执行若干 action。

原因是家务、购物这类任务里大量动作是机械的("走到桌子旁""打开抽屉"),每步都强行推理只会浪费 token 并增加跑偏概率。这是个实用的调参维度:任务越机械,thought 越该稀疏。

三、ReAct 的 Prompt 长什么样?

论文在 HotpotQA / FEVER 这两个知识问答任务里搭了一个基于 Wikipedia API 的环境,动作空间就三个动作:

动作 含义
search[entity] 搜索实体,返回对应词条前若干句
lookup[string] 在当前词条内查找包含该字符串的下一句(模拟浏览器 Ctrl+F)
finish[answer] 终止并给出答案

⚠️ 把这三个动作当成 ReAct 的通用动作空间 错误认知: "ReAct 就是 search / lookup / finish 这三个动作",于是实现自己的 Agent 时也照着搬一个 finish[...] 动作,或者觉得动作必须长成 动作名[参数] 这个样子。

实际情况: 这三个动作属于那个 Wikipedia 环境,不属于 ReAct。同一篇论文里的另外两个任务用的是完全不同的动作集:ALFWorld(文字版家务)用的是 go totakeopenclean 这类具身动作;WebShop(网购)用的是 searchclick 这类网页操作。它们都不用 finish[answer] 结束——任务在环境判定目标达成时自然终止。

原因: ReAct 规定的是交互结构(Thought / Action / Observation 交错),动作空间是环境给的。混淆这两层,就会把一个具体实验的细节当成范式要求。

正确做法: 设计自己的 Agent 时,动作空间从你的业务环境推导——有哪些工具、每个工具收什么参数。终止方式也一样:可以是一个显式的 finish 动作,也可以是"模型不再请求工具调用"(这正是今天 Function Calling 的做法),还可以是环境侧的目标判定。

Prompt 本身是 few-shot 的,结构如下:

Solve a question answering task with interleaving Thought, Action, Observation steps.
Action can be three types:
(1) search[entity]  (2) lookup[string]  (3) finish[answer]
 
Question: 姚明和奥尼尔谁更高?高多少?
Thought 1: 我需要分别查姚明和奥尼尔的身高,再做差值比较。先查姚明。
Action 1: search[姚明]
Observation 1: 姚明,中国前职业篮球运动员,身高 2.26 米……
Thought 2: 姚明 2.26 米。现在查奥尼尔。
Action 2: search[沙奎尔·奥尼尔]
Observation 2: 沙奎尔·奥尼尔,身高 2.16 米……
Thought 3: 2.26 > 2.16,姚明更高,差值 0.10 米。信息已完整。
Action 3: finish[姚明更高,比奥尼尔高约 0.10 米]
 
Question: {新问题}

💡 这里模型学到的不是知识,而是输出格式和思考节奏。这就是"范式"一词的含义:它规定的是交互结构,不是任务内容。

⚠️ 不要把 ReAct 的强度全部归给 Prompt 设计 常见说法: "ReAct 强不是模型强,是 Prompt 设计天才。"

实际情况: 论文的主要实验建立在 PaLM-540B 上,并明确指出 ReAct 的收益依赖模型规模——小模型套用同样的 Prompt 效果显著劣化,甚至不如直接微调。

原因: 交错生成推理与结构化动作,要求模型同时具备长程规划能力和严格的格式遵循能力,这两项都是随规模涌现的。

正确理解: Prompt 结构是必要条件,模型能力是前提条件。把它当成"小模型也能靠 Prompt 变强"的方法会踩坑。

四、落地时最容易写错的地方:停止符

这是自己动手实现 ReAct 时第一个会踩、且踩了很难察觉的坑。

⚠️ 不设 stop sequence,模型会自己编造 Observation 错误操作: 直接调 llm(prompt),让模型自由生成到结束。

实际结果: 模型会一口气把 Thought 1 / Action 1 / Observation 1 / Thought 2 / Action 2 … 全部生成出来,包括本该由真实工具返回的 Observation。日志看上去完美无缺,但所有"观察"都是模型幻觉出来的,一次工具都没真正调用。

原因: few-shot 示例里 Observation 就紧跟在 Action 后面。对模型而言这只是一个待续写的文本模式,它没有任何理由在此停笔——是我们外部要拦截它。

正确做法:\nObservation 设为停止符,强制模型每轮只生成到 Action 为止:

text = llm(prompt, stop=["\nObservation"])   # 关键:截断在这里

带上这个细节后,最小实现是这样的:

MAX_STEPS = 8
 
def react(question: str) -> str:
    history = ""
    for step in range(1, MAX_STEPS + 1):
        prompt = f"{FEWSHOT_PROMPT}\nQuestion: {question}\n{history}Thought {step}:"
 
        # 1. 生成本轮的 Thought + Action,停在 Observation 之前
        text = llm(prompt, stop=["\nObservation"])
 
        # 2. 从文本里解析出动作名和参数
        name, arg = parse_action(text)          # 如 ("search", "姚明")
 
        if name == "finish":
            return arg
 
        # 3. 真实执行工具,结果由外部写回,绝不让模型自己生成
        observation = TOOLS[name](arg)
 
        history += f"Thought {step}:{text}\nObservation {step}: {observation}\n"
 
    return "达到最大步数仍未得出结论"

这段代码里有三个决策点值得留意:停止符位置决定观察是否真实、finish 的判断位置决定终止逻辑是否唯一、MAX_STEPS 决定失控时能否收场。

五、ReAct 真的比 CoT 强吗?

这是最容易被科普文章带偏的一节。直觉上"能查资料的当然比只会背书的强",但论文数据并非如此。

以 HotpotQA(多跳问答,PaLM-540B)为例,精确匹配得分大致是:

方法 HotpotQA EM 说明
CoT ≈29.4 只推理,不查资料
CoT-SC ≈33.4 CoT + 自洽性投票
Act ≈25.7 只行动,无推理
ReAct ≈27.4 单独使用时低于 CoT
ReAct + CoT-SC 混合 ≈35.1 论文中的最佳配置

而在 ALFWorld(文字版家务决策任务)上,差距则完全反过来:ReAct 成功率约 71%,Act 约 45%,差了 26 个百分点。

这两组数据合起来说明什么 ReAct 的优势不在"知识问答准确率",而在"需要与环境多步交互的任务"。

在 HotpotQA 上 ReAct 落后于 CoT,是因为它被检索结果牢牢约束住了——搜到的段落不相关时,它不像 CoT 那样敢于用内部知识推理,反而被带偏。

论文因此提出混合策略:先跑 ReAct,若在规定步数内没有 finish,就回退到 CoT-SC;或反过来,CoT-SC 内部投票分歧大时切到 ReAct 查证。内部知识与外部知识各有适用面,切换才是最优解。

⚠️ "所有事实都来自 Observation,所以不会幻觉"是过强的结论 常见说法: ReAct 能彻底抗幻觉。

实际结果: 论文的错误分析里,ReAct 有相当比例的失败来自推理错误检索到无信息量的结果。观察是真的,但模型对观察的解读可能是错的。

原因: ReAct 只保证「观察这一环」接地(grounded),不保证「从观察到结论这一环」正确。而且检索工具本身也会返回错误或过时内容。

正确表述: ReAct 显著降低事实性幻觉,并让幻觉可追溯(能从轨迹里看出是哪一步开始跑偏的),但不消除幻觉。

六、ReAct 的失败模式与代价

问题 具体表现 根因
调用次数多 N 步任务需要 N+1 次 LLM 调用,且每次重传全部历史 交错结构决定每个 action 后必须回到模型
延迟与成本高 上下文随步数线性膨胀,token 消耗接近平方级增长 历史全量拼接进 Prompt
重复动作死循环 反复用同一关键词搜索、反复生成相同 thought 模型无法感知"我刚才试过且失败了"
缺乏全局规划 走一步看一步,长任务中途迷路 没有独立的规划层,每步只看局部
格式脆弱 自由文本输出的 Action: xxx[yyy] 解析失败 纯文本约定,无结构化保证

⚠️ 重复动作死循环必须由外部打断 错误操作: 只设最大步数,指望模型自己意识到在重复。

实际结果: 模型会把"搜索失败 → 换个说法再搜 → 又失败"重复到步数耗尽,白白烧掉全部预算。

原因: 上一轮的失败在历史里只是一段普通文本,模型没有被显式要求去比对"这个动作我是否已经执行过"。

正确做法: 在循环外维护已执行动作的集合,命中重复时主动把提示写进 Observation,让模型看到:

key = (name, arg)
if key in seen:
    observation = f"你已经执行过 {name}[{arg}] 且结果无效,请换一个动作或参数。"
else:
    seen.add(key)
    observation = TOOLS[name](arg)

关键在于反馈要走 Observation 通道——那是模型唯一会认真读的外部输入。

七、后来者如何改进 ReAct?

react-evolution-llm-calls

上图按行对照四种调用模式,蓝框是 LLM 调用、橙框是工具执行。读图重点是数蓝框个数:ReAct 每做一步就得回一次模型,ReWOO 把它压到两次。

ReWOO(Reasoning WithOut Observation,2023) 针对的是调用次数。它拆成 Planner / Worker / Solver 三段:Planner 一次性输出完整计划,计划中用 #E1#E2 这类变量占位来引用前序步骤的结果;Worker 按计划执行工具,中途不再回到 LLM;最后 Solver 汇总成答案。典型情况下 LLM 调用从 N+1 次降到 2 次。

⚠️ ReWOO 不等于"所有工具并行执行" 计划里有依赖关系的步骤(第 3 步要用第 1 步的结果)仍然必须按序执行。ReWOO 省掉的是工具之间那些回到 LLM 的往返,不是工具执行本身的顺序性。代价也很直接:计划一次成型,中途拿到意外结果时无法调整策略。

Reflexion(2023) 针对的是失败经验不留存。任务失败后,让模型生成一段自然语言反思("上次因为关键词过于宽泛而失败,这次应更具体"),写入情景记忆,下一轮重跑时带上。论文称之为言语强化学习(verbal reinforcement learning)——用文本反馈替代梯度更新。

🆚 Reflexion 治的不是单轮死循环 它作用在轮次之间:整个任务失败一次,反思一次,再重试整个任务。而第六节说的重复动作,发生在单轮内部,得靠循环保护解决。两者常被混淆。

Plan-and-Execute 针对的是缺乏全局规划。先由 Planner 模型产出高层步骤清单,每个子任务再交给 ReAct 子循环去执行,执行完可以重新规划(replan)剩余步骤。在这个架构里 ReAct 降级成了执行层,不再负责全局路线。

八、ReAct 和 Function Calling 是什么关系?

⚠️ "现在的模型联网搜索,背后跑的就是 ReAct"并不准确 常见说法: 今天用的 GPT、Claude 一联网,内部就是在跑 ReAct 的 few-shot Prompt。

实际情况: 现代模型的工具调用是训练进模型里的原生能力,通过专门的消息角色和结构化 JSON 表达,不依赖 ReAct 那套 few-shot 文本模板。

原因: ReAct 用自由文本表达动作(Action: search[姚明]),解析全靠正则,模型稍微换个写法就崩。厂商把这层约定收进训练和 API 契约,改成结构化输出,可靠性完全不是一个量级。

正确表述: Function Calling 继承了 ReAct 的思想内核(模型决策 → 外部执行 → 结果回流 → 继续决策),但替换了它的实现载体。说"血脉相承"没问题,说"背后跑的就是 ReAct"就错了。

🆚 两者的同维度对照:

维度 ReAct(原始) Function Calling
动作表达 自由文本 Action: search[x] 结构化 {"name":"search","arguments":{...}}
能力来源 few-shot Prompt 诱导 模型训练时习得
解析方式 正则/字符串切分,易失败 JSON 解析,schema 约束
推理痕迹 Thought 显式写在文本里 可有可无(部分模型放在 reasoning 字段)
停止控制 靠 stop sequence 人工截断 API 层面通过 finish_reason 天然分段
并行调用 不支持 多数厂商支持一次返回多个调用
相同点 决策与执行分离、结果回流后继续决策的闭环 同左

💡 实践建议:如果模型支持原生 Function Calling,不要再手写 ReAct 文本模板。只在这两种情况下退回原始 ReAct:使用不支持工具调用的开源模型;教学与调试。

⚠️ 「为了审计所以要保留完整 Thought」是个站不住的理由 这条曾经被列为退回 ReAct 的第三个理由,但它把 Thought 的证明力估高了。

Thought 是模型生成的文本,不保证忠实反映它内部真实的决策依据。 Turpin 等人的实验(arXiv 2305.04388)已经证明:模型的答案可以明显被注入的偏置带偏,而它写出来的推理链只字不提这个偏置,转而编造一套合情合理的说辞。详见 CoT 笔记

所以拿 Thought 去做审计,审的是模型的自述,不是它的实际行为。一段流畅的 Thought 反而会让错误决策显得更可信。

审计真正该落盘的是:调用了哪个工具、传了什么参数、返回了什么、什么时间、由谁授权。这些是可核对的事实,而且 Function Calling 的结构化输出天然就带这些字段,比解析自由文本更可靠。Thought 留着当调试线索很有价值,但它不是证据。

顺带一提,现在多数厂商也把推理内容放在专门的 reasoning 字段里返回,「要看推理过程就得手写 ReAct」这个前提本身也不再成立了。

常见说法订正

整理一份高频误传对照,面试和写文档时容易踩:

常见说法 是否准确 订正
ReAct 2022 年 10 月由普林斯顿 + Google 提出 ✅ 准确 arXiv 2210.03629,ICLR 2023 接收
输出格式是 Thought / Action / Observation / Finish ❌ 不准确 finish[answer] 是一个 Action,不是独立标签
ReAct 通过 Observation 彻底消除幻觉 ❌ 过强 只保证观察接地,推理仍可能出错;应说"显著降低且可追溯"
ReAct 全面优于 CoT ❌ 不准确 HotpotQA 上单用 ReAct 反而低于 CoT,混合策略才最优
ReAct 强在 Prompt 设计,与模型无关 ❌ 不准确 论文明确指出收益依赖模型规模(PaLM-540B)
后续框架"全部照抄"ReAct ⚠️ 夸大 是主流内核之一,但 AutoGPT 等还依赖任务队列与长期记忆,并非纯 ReAct
今天模型联网搜索背后跑的就是 ReAct ❌ 不准确 是原生 Function Calling,思想继承但载体不同
ReAct 的动作空间是 search / lookup / finish ❌ 错误 那是 HotpotQA/FEVER 的 Wikipedia 环境专有;ALFWorld、WebShop 用完全不同的动作集
必须暴露完整 Thought 才能审计 Agent ❌ 错误 Thought 不保证忠实。审计要落盘工具调用与返回,Thought 只是调试线索
ReWOO 一次性并行执行所有工具 ⚠️ 不严谨 有依赖的步骤仍按序执行,省掉的是回 LLM 的往返
Reflexion 解决死循环 ⚠️ 偏差 它解决跨轮次的经验积累;单轮死循环靠循环保护
Let's think step by step 出自 CoT 原论文 ❌ 不准确 出自 Zero-shot CoT(Kojima et al., 2022)

速查表

概念 要点
ReAct 全称 Reasoning + Acting,强调 Synergizing(协同)
核心循环 Thought → Action → Observation → Thought → … → 终止
Wikipedia 实验的动作空间 search[entity] / lookup[string] / finish[answer]——只属于 HotpotQA/FEVER 环境,ALFWorld、WebShop 各有各的动作集
动作空间由谁决定 环境,不是 ReAct。ReAct 只规定 Thought/Action/Observation 的交错结构
实现第一坑 必须设 stop=["\nObservation"],否则模型自编观察结果
实现第二坑 必须有 MAX_STEPS 和重复动作检测
Thought 密度 问答任务严格交替;决策任务可稀疏插入
最强配置 ReAct 与 CoT-SC 互为回退,而非单用
成本模型 N 步 ≈ N+1 次 LLM 调用,上下文随步数膨胀
三个改进方向 ReWOO(省调用)/ Reflexion(存经验)/ Plan-Execute(补规划)
今天怎么用 有原生 Function Calling 就用它,思想仍是 ReAct 的闭环

复习重点

复习重点

  1. 一句话说清 ReAct:交替生成推理与行动,让 Thought → Action 赋予行动目的,让 Observation → Thought 提供纠偏依据;两个方向缺一即退化。
  2. 最容易写错的实现细节:不设停止符,模型会连 Observation 一起编出来,全流程零真实工具调用,且日志毫无异常。
  3. 最容易记错的格式finish 是动作之一,不是与 Action 平级的字段——但它本身也只是 Wikipedia 环境的设定,不是 ReAct 要求每个实现都有 finish 动作
  4. 最容易过度泛化的地方search / lookup / finish 是那个实验环境的动作空间,不是 ReAct 的。动作空间来自环境,ReAct 只规定交互结构。
  5. 最容易被科普带偏的结论:ReAct 不是全面碾压 CoT。它在多步环境交互任务上优势巨大,在纯知识问答上单用反而可能不如 CoT,混合才最优。
  6. Thought 不是审计凭证:它是模型生成的文本,不保证忠实。要审计就落盘工具调用与返回值。
  7. 选型规则:模型支持原生 Function Calling → 直接用;模型不支持 → 手写 ReAct;调用成本敏感且流程稳定 → 考虑 ReWOO;任务长且易迷路 → Plan-and-Execute 把 ReAct 降为执行层。

动手练习

前面第四节的最小实现只有 MAX_STEPS 这一道防线,不足以应对第六节说的重复动作死循环。

练习:在 react() 循环中加入循环保护,需要自己决定三件事——用什么粒度判定"重复"(动作名?动作名加参数?还是参数的语义相似度)、重复多少次才触发、触发后是直接中止还是把提示写回 Observation 让模型自救。三种选择各有代价,没有唯一答案。


一句话总结 ReAct 的贡献不是"让模型会用工具",而是把推理和行动放进同一个生成序列里,让它们互为输入。今天绝大多数工具型 Agent 的主循环,无论外面套了多少层规划和记忆,最内核仍是这个闭环。

但别把它读成"所有 Agent 都是 ReAct":预定义步骤的 Workflow 没有这个循环(下一步走哪由代码决定,不由模型决定);Plan-and-Execute 把"想"和"做"拆成了两个阶段,执行期不再每步重新推理;多 Agent 编排层调度的是 Agent 而不是工具。ReAct 是**"模型自己决定下一步动作"这一类**系统的内核,不是 Agent 这个词的全部。